

/*
 * En este archivo implementamos la funcionalidad del nodo coordinador.
 */

#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <mpi.h>

#include "coordinador.h"
#include "ordenamiento-final.h"
#include "envio-vector.h"
#include "plan-trabajo.h"

#include "../nodo/nodo.h"
#include "../varios/log.h"
#include "../varios/mensajes.h"
#include "../varios/mpi-utiles.h"

#include "../../compartido/vectores.h"
#include "../../compartido/entrada-salida.h"



struct VECTOR *ordenarVectorEnParalelo(struct VECTOR *vector);
void enviarAsignaciones(int cantNodos, struct ASIGNACION_NODO **asignaciones);
int calcularTamanioAsignacion(struct ASIGNACION_NODO *asignacion);
char *convertirAsignacionEnBloque(struct ASIGNACION_NODO *asignacion, int tamAsignacion);


/*
 * Implementacion de función definida en cabecera
 */
double *coordinador() {

    struct VECTOR *vectorEnDisco; // Vector a ordenar
    double instante0; // Instante previo a la obtencion del vector
    double instante1; // Instante posterior a la obtencion del vector
    double instante2; // Instante anterior a la eliminacion del vector
    double instante3; // Instante posterior a la eliminacion del vector

    // Obtenemos el vector a ordenar, registramos el instante inicial, y ordenamos el vector
    LOGGER("Obtenemos el vector del usuario");
    instante0 = MPI_Wtime();
    vectorEnDisco = obtenerVectorDelUsuario();
    instante1 = MPI_Wtime();
    LOGGER("Vector de usuario obtenido %s", imprimirVector(vectorEnDisco));
    
    // Ordenamos el vector en paralelo
    struct VECTOR *vectorOrdenado = ordenarVectorEnParalelo(vectorEnDisco);
    eliminarVector(vectorEnDisco); // Eliminamos el vector obtenido
    LOGGER("Vector ordenado %s", imprimirVector(vectorOrdenado));

    // Mostramos el vector por pantalla
    mostrarVector(vectorOrdenado);
    
    // Guardamos el vector ordenado
    LOGGER("Almacenamos el vector ordenado");
    instante2 = MPI_Wtime();
    almacenarVector(vectorOrdenado, "vector ordenado");
    eliminarVector(vectorOrdenado);
    instante3 = MPI_Wtime();
    
    // Calculamos los tiempos de espera y no contabilizables
    double *tiempos = malloc(sizeof(double)*2);
    tiempos[0] = (instante1-instante0) + (instante3-instante2);
    tiempos[1] = obtenerTiempoEspera();

    LOGGER("Fin del metodo coordinador");
    return tiempos;

}


/*
 * Ordena el vector pasado por parametro utilizando el algoritmo de mergesort,
 * en forma paralela.
 */
 struct VECTOR *ordenarVectorEnParalelo(struct VECTOR *vector) {

    // Necesitamos saber algunos datos generales
    int cantNodos = obtenerCantidadDeNodosDeTrabajo();
    long long dimension = obtenerDimension(vector);
    LOGGER("Cantidad de nodos de trabajo: %d Dimension: %l", cantNodos);

    struct ESPEC_TRABAJO_COORD especTrabajo = obtenerPlanTrabajo(cantNodos, dimension);
    LOGGER("Espec de trabajo obtenido: %s", imprimirEspecTrabajoCoord(&especTrabajo, cantNodos));
    
    LOGGER("Enviamos los asignaciones a los nodos");
    enviarAsignaciones(cantNodos, especTrabajo.asignaciones);
    LOGGER("Asignaciones enviados");
    
    LOGGER("Enviamos lo establecido en los asignaciones");
    enviarVector(cantNodos, vector, especTrabajo.asignaciones);
    LOGGER("Datos enviados de acuerdo al asignacion");
    
    LOGGER("Recibimos y ordenamos los 2 ultimos vectores");
    struct VECTOR *vectorOrdenado = recibirOrdenarUltimosVectores(especTrabajo.nodosFinales, especTrabajo.tamNodosFinales);
    LOGGER("Ultimos 2 vectores recibidos y ordenados");

    return vectorOrdenado;

}


/*
 * Envia los asignaciones a los nodos
 */
void enviarAsignaciones(int cantNodos, struct ASIGNACION_NODO **asignaciones) {
    
    MPI_Request requests[cantNodos];
    
    int i;
    for (i=0;i<cantNodos;i++) {
        
        int idNodo = i+1; // Obtenemos el destino de esta iteración
        struct ASIGNACION_NODO *trabajoActual = asignaciones[i]; // Obtenemos el trabajo a enviar en esta iteracion
        LOGGER("Al nodo %d, le enviaremos el asignacion: %s", idNodo, imprimirAsignacion(trabajoActual));
        
        // Obtenemos la cantidad de elementos del trabajo y lo convertimos en un bloque
        int tamCronog = calcularTamanioAsignacion(trabajoActual);
        char *bloque = convertirAsignacionEnBloque(trabajoActual, tamCronog);
        LOGGER("Enviaremos un bloque de cantidad de bytes %d", tamCronog);
        
        // Enviamos el trabajo de manera asincrona
        MPI_Isend(bloque, tamCronog, MPI_INT, idNodo, MSG_ASIGNACION, MPI_COMM_WORLD, &requests[i]);
        LOGGER("Asignacion en bloque enviado");
        
    }
    
}


int calcularTamanioAsignacion(struct ASIGNACION_NODO *asignacion) {

    // Calculamos el tamaño total del asignacion
    int cantInterm = asignacion->cantOriginesIntermedios;
    int tamAsignacion = 0;
    
    tamAsignacion += sizeof(int);                 // cantOriginesIntermedios
    tamAsignacion += sizeof(int) * cantInterm;    // originesIntermedios
    tamAsignacion += sizeof(long long) * cantInterm; // tamVectoresIntermedios
    tamAsignacion += sizeof(long long);           // tamVectorInicial
    tamAsignacion += sizeof(int);                 // destinoFinal
    
    return tamAsignacion;
    
}


char *convertirAsignacionEnBloque(struct ASIGNACION_NODO *asignacion, int tamAsignacion) {

    // Creamos el array de bytes que representara al asignacion
    char *bloque = malloc(tamAsignacion);
    char *ptrBloque = bloque;
    
    int cantInterm = asignacion->cantOriginesIntermedios;
    
    // Copiamos la propiedad cantidadOrigenesIntermedios
    int tam1 = sizeof(int);
    memcpy(ptrBloque, &asignacion->cantOriginesIntermedios, tam1);
    ptrBloque += tam1;
    
    // Copiamos la propiedad origenesIntermedios
    int tam2 = sizeof(int) * cantInterm;
    memcpy(ptrBloque, asignacion->originesIntermedios, tam2);
    ptrBloque += tam2;
    
    // Copiamos la propiedad tamVectoresIntermedios
    int tam3 = sizeof(long long) * cantInterm;
    memcpy(ptrBloque, asignacion->tamVectoresIntermedios, tam3);
    ptrBloque += tam3;
    
    // Copiamos la propiedad tamVectorInicial
    int tam4 = sizeof(long long);
    memcpy(ptrBloque, &asignacion->tamVectorInicial, tam4);
    ptrBloque += tam4;
    
    // Copiamos la propiedad destinoFinal
    int tam5 = sizeof(int);
    memcpy(ptrBloque, &asignacion->destinoFinal, tam5);
    ptrBloque += tam5;
    
    return bloque;
    
}
